﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NConf.Interfaces;
using System.Threading;

using NConf.ConfigSources;

namespace NConf
{
    public class ConfigManager : DictConfigSource
    {
        //---------------------------------------------------------------------

        #region Properties

        /// <summary>
        /// A global ConfigManager instance.
        /// </summary>
        /// <remarks>
        /// This global instance is what most applications will use, however we do retain the ability for individual
        /// ConfigManager instances to be created.
        /// </remarks>
        public static ConfigManager GlobalInstance
        {
            get
            {
                if (_globalInstance == null)
                {
                    _globalInstance = new ConfigManager();
                } // end if

                return _globalInstance;
            } // end get
        } // end GlobalInstance

        #endregion

        //---------------------------------------------------------------------

        #region Internal Members

        /// <summary>
        /// The global config manager instance.
        /// </summary>
        private static ConfigManager _globalInstance = null;

        /// <summary>
        /// A list of all our current config sources.
        /// </summary>
        private List<IConfigSource> Sources = new List<IConfigSource>();

        /// <summary>
        /// A Read/Write thread lock
        /// </summary>
        private ReaderWriterLockSlim confLock = new ReaderWriterLockSlim();

        #endregion

        //---------------------------------------------------------------------

        #region Constructors

        /// <summary>
        /// Default Constructor.
        /// </summary>
        public ConfigManager(List<IConfigSource> sources = null)
        {
            Configuration = new Dictionary<string, object>();

            if (sources != null)
            {
                Sources = sources;
                UpdateConfig();
            } // end if
        } // end ConfigManager

        #endregion

        //---------------------------------------------------------------------

        #region External API

        /// <summary>
        /// Adds a new IConfigSource to the config manager.
        /// </summary>
        /// <remarks>
        /// This triggers an update of all the configuration, which could be intensive.
        /// </remarks>
        /// <param name="source">The IConfigSource to add.</param>
        /// <param name="priority">The priority at wich we're adding this config source. Negative values simply cause
        /// the soure to be added at lowest priority. Defaults to -1.</param>
        public void AddConfigSource(IConfigSource source, int priority = -1)
        {
            // Aquire Write Lock
            confLock.EnterWriteLock();
            try
            {
                if (priority > 0)
                {
                    Sources.Insert(priority, source);
                }
                else
                {
                    Sources.Add(source);
                } // end if
            }
            finally { confLock.ExitWriteLock(); }

            UpdateConfig();
        } // end AddConfigSource

        /// <summary>
        /// Removes an IConfigSource from the config manager.
        /// </summary>
        /// <remarks>
        /// This triggers an update of all the configuration, which could be intensive.
        /// </remarks>
        /// <param name="source">The IConfigSource to remove.</param>
        public void RemoveConfigSource(IConfigSource source)
        {
            // Aquire Write Lock
            confLock.EnterWriteLock();
            try
            {
                Sources.Remove(source);
            }
            finally { confLock.ExitWriteLock(); }

            UpdateConfig();
        } // end RemoveConfigSource

        /// <summary>
        /// Retrieves the IConfigSource providing the given key.
        /// </summary>
        /// <remarks>
        /// The IConfigSource returned is the provider of the _current value_ of the key.
        /// </remarks>
        /// <param name="configPath">The config path key to find the provider for.</param>
        /// <returns>The IConfigSource providing the current value of that key.</returns>
        public IConfigSource GetSourceProvidingKey(string configPath)
        {
            foreach (IConfigSource source in Sources)
            {
                if (source.HasValue(configPath))
                {
                    return source;
                } // end if
            } // end foreach

            return null;
        } // end GetSourceProvidingKey

        #endregion

        //---------------------------------------------------------------------

        #region Internal

        /// <summary>
        /// Update the internal config cache.
        /// </summary>
        private void UpdateConfig()
        {
            Configuration.Clear();

            // Reverse the list so that index 0 is highest priority.
            var sources = new List<IConfigSource>(Sources);
            sources.Reverse();

            // Iterate over all the sources, and apply their config.
            foreach (var source in sources)
            {
                foreach (var kvp in source.Configuration)
                {
                    SetValue(kvp);
                } // end foreach
            } // end foreach
        } // end updateConfigDictionary

        #endregion

        //---------------------------------------------------------------------
    } // end ConfigManager
} // end NConf namespace
